{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Pandas 字符串查找和匹配\n",
        "\n",
        "本教程介绍Pandas中字符串查找和匹配的相关知识点和代码案例，包括`str.contains()`、`str.startswith()`、`str.endswith()`、`str.find()`、`str.rfind()`、`str.index()`和`str.rindex()`方法的使用。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 学习目标\n",
        "\n",
        "- 掌握`str.contains()`方法检查字符串是否包含子串\n",
        "- 掌握`str.startswith()`和`str.endswith()`方法检查字符串开头和结尾\n",
        "- 掌握`str.find()`和`str.rfind()`方法查找子串位置\n",
        "- 掌握`str.index()`和`str.rindex()`方法查找子串索引\n",
        "- 了解正则表达式在查找中的应用\n",
        "- 学习实际应用案例\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 1. 导入库\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {},
      "outputs": [],
      "source": [
        "import pandas as pd\n",
        "import numpy as np\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 2. str.contains() - 检查是否包含子串\n",
        "\n",
        "`str.contains()`方法用于检查Series中每个字符串是否包含指定的子串或正则表达式模式，返回布尔Series。\n",
        "\n",
        "### 语法\n",
        "```python\n",
        "series.str.contains(pat, case=True, na=nan, regex=True)\n",
        "```\n",
        "\n",
        "### 参数\n",
        "- `pat`: 要查找的子串或正则表达式模式\n",
        "- `case`: 是否区分大小写（默认True）\n",
        "- `na`: 处理NaN值的方式（默认NaN）\n",
        "- `regex`: 是否使用正则表达式（默认True）\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 2.1 基本用法\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "原始数据:\n",
            "0      Python Programming\n",
            "1    Pandas Data Analysis\n",
            "2            NumPy Arrays\n",
            "3        Machine Learning\n",
            "dtype: object\n",
            "\n",
            "是否包含'Python':\n",
            "0     True\n",
            "1    False\n",
            "2    False\n",
            "3    False\n",
            "dtype: bool\n",
            "\n",
            "是否包含'Data':\n",
            "0    False\n",
            "1     True\n",
            "2    False\n",
            "3    False\n",
            "dtype: bool\n",
            "1    Pandas Data Analysis\n",
            "dtype: object\n"
          ]
        }
      ],
      "source": [
        "\n",
        "# 创建示例数据\n",
        "data = pd.Series(['Python Programming', 'Pandas Data Analysis', 'NumPy Arrays', 'Machine Learning'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 检查是否包含'Python'\n",
        "print(\"\\n是否包含'Python':\")\n",
        "print(data.str.contains('Python'))\n",
        "\n",
        "# 检查是否包含'Data'\n",
        "print(\"\\n是否包含'Data':\")\n",
        "print(data.str.contains('Data'))\n",
        "print(data[data.str.contains('Data')])\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 2.2 不区分大小写\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "原始数据:\n",
            "0    Python\n",
            "1    python\n",
            "2    PYTHON\n",
            "3    pandas\n",
            "4    Pandas\n",
            "dtype: object\n",
            "\n",
            "区分大小写查找'Python':\n",
            "0     True\n",
            "1    False\n",
            "2    False\n",
            "3    False\n",
            "4    False\n",
            "dtype: bool\n",
            "\n",
            "不区分大小写查找'Python':\n",
            "0     True\n",
            "1     True\n",
            "2     True\n",
            "3    False\n",
            "4    False\n",
            "dtype: bool\n"
          ]
        }
      ],
      "source": [
        "# 创建包含大小写混合的数据\n",
        "data = pd.Series(['Python', 'python', 'PYTHON', 'pandas', 'Pandas'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 区分大小写（默认）\n",
        "print(\"\\n区分大小写查找'Python':\")\n",
        "print(data.str.contains('Python', case=True))\n",
        "\n",
        "# 不区分大小写\n",
        "print(\"\\n不区分大小写查找'Python':\")\n",
        "print(data.str.contains('Python', case=False))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 2.3 使用正则表达式\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "邮件地址:\n",
            "0    alice@example.com\n",
            "1         bob@test.com\n",
            "2    charlie@gmail.com\n",
            "3      david@yahoo.com\n",
            "dtype: object\n",
            "\n",
            "是否包含gmail邮箱:\n",
            "0    False\n",
            "1    False\n",
            "2     True\n",
            "3    False\n",
            "dtype: bool\n",
            "\n",
            "文本数据:\n",
            "0    abc123\n",
            "1    def456\n",
            "2       ghi\n",
            "3    jkl789\n",
            "dtype: object\n",
            "\n",
            "是否包含数字:\n",
            "0     True\n",
            "1     True\n",
            "2    False\n",
            "3     True\n",
            "dtype: bool\n"
          ]
        }
      ],
      "source": [
        "# 创建示例数据\n",
        "emails = pd.Series(['alice@example.com', 'bob@test.com', 'charlie@gmail.com', 'david@yahoo.com'])\n",
        "print(\"邮件地址:\")\n",
        "print(emails)\n",
        "\n",
        "# 使用正则表达式查找gmail邮箱\n",
        "print(\"\\n是否包含gmail邮箱:\")\n",
        "print(emails.str.contains(r'@gmail\\.com', regex=True))\n",
        "\n",
        "# 查找包含数字的字符串\n",
        "texts = pd.Series(['abc123', 'def456', 'ghi', 'jkl789'])\n",
        "print(\"\\n文本数据:\")\n",
        "print(texts)\n",
        "print(\"\\n是否包含数字:\")\n",
        "print(texts.str.contains(r'\\d', regex=True))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 2.4 处理NaN值\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "包含NaN的数据:\n",
            "0    Python\n",
            "1    Pandas\n",
            "2       NaN\n",
            "3     NumPy\n",
            "4      None\n",
            "dtype: object\n",
            "\n",
            "默认处理（NaN返回NaN）:\n",
            "0     True\n",
            "1    False\n",
            "2      NaN\n",
            "3    False\n",
            "4     None\n",
            "dtype: object\n",
            "\n",
            "NaN返回False:\n",
            "0     True\n",
            "1    False\n",
            "2    False\n",
            "3    False\n",
            "4    False\n",
            "dtype: bool\n"
          ]
        }
      ],
      "source": [
        "# 包含NaN的数据\n",
        "data_with_nan = pd.Series(['Python', 'Pandas', np.nan, 'NumPy', None])\n",
        "print(\"包含NaN的数据:\")\n",
        "print(data_with_nan)\n",
        "\n",
        "# 默认情况下，NaN返回NaN\n",
        "print(\"\\n默认处理（NaN返回NaN）:\")\n",
        "print(data_with_nan.str.contains('Python'))\n",
        "\n",
        "# 指定NaN返回False\n",
        "print(\"\\nNaN返回False:\")\n",
        "print(data_with_nan.str.contains('Python', na=False))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 3. str.startswith() - 检查是否以某字符串开头\n",
        "\n",
        "`str.startswith()`方法用于检查Series中每个字符串是否以指定的子串开头。\n",
        "\n",
        "### 语法\n",
        "```python\n",
        "series.str.startswith(pat, na=nan)\n",
        "```\n",
        "\n",
        "### 参数\n",
        "- `pat`: 要检查的前缀字符串\n",
        "- `na`: 处理NaN值的方式（默认NaN）\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "原始数据:\n",
            "0    Python Programming\n",
            "1       Pandas Analysis\n",
            "2         Python Basics\n",
            "3          NumPy Arrays\n",
            "dtype: object\n",
            "\n",
            "是否以'Python'开头:\n",
            "0     True\n",
            "1    False\n",
            "2     True\n",
            "3    False\n",
            "dtype: bool\n",
            "\n",
            "是否以'P'开头:\n",
            "0     True\n",
            "1     True\n",
            "2     True\n",
            "3    False\n",
            "dtype: bool\n"
          ]
        }
      ],
      "source": [
        "# 创建示例数据\n",
        "data = pd.Series(['Python Programming', 'Pandas Analysis', 'Python Basics', 'NumPy Arrays'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 检查是否以'Python'开头\n",
        "print(\"\\n是否以'Python'开头:\")\n",
        "print(data.str.startswith('Python'))\n",
        "\n",
        "# 检查是否以'P'开头\n",
        "print(\"\\n是否以'P'开头:\")\n",
        "print(data.str.startswith('P'))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 4. str.endswith() - 检查是否以某字符串结尾\n",
        "\n",
        "`str.endswith()`方法用于检查Series中每个字符串是否以指定的子串结尾。\n",
        "\n",
        "### 语法\n",
        "```python\n",
        "series.str.endswith(pat, na=nan)\n",
        "```\n",
        "\n",
        "### 参数\n",
        "- `pat`: 要检查的后缀字符串\n",
        "- `na`: 处理NaN值的方式（默认NaN）\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "文件名:\n",
            "0    document.pdf\n",
            "1       image.jpg\n",
            "2       data.xlsx\n",
            "3       script.py\n",
            "4      readme.txt\n",
            "dtype: object\n",
            "\n",
            "是否以'.pdf'结尾:\n",
            "0     True\n",
            "1    False\n",
            "2    False\n",
            "3    False\n",
            "4    False\n",
            "dtype: bool\n",
            "\n",
            "是否以'.py'结尾:\n",
            "0    False\n",
            "1    False\n",
            "2    False\n",
            "3     True\n",
            "4    False\n",
            "dtype: bool\n"
          ]
        }
      ],
      "source": [
        "# 创建示例数据\n",
        "files = pd.Series(['document.pdf', 'image.jpg', 'data.xlsx', 'script.py', 'readme.txt'])\n",
        "print(\"文件名:\")\n",
        "print(files)\n",
        "\n",
        "# 检查是否以'.pdf'结尾\n",
        "print(\"\\n是否以'.pdf'结尾:\")\n",
        "print(files.str.endswith('.pdf'))\n",
        "\n",
        "# 检查是否以'.py'结尾\n",
        "print(\"\\n是否以'.py'结尾:\")\n",
        "print(files.str.endswith('.py'))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 5. str.find() - 查找子串位置\n",
        "\n",
        "`str.find()`方法用于查找子串在字符串中的位置，如果找不到返回-1。\n",
        "\n",
        "### 语法\n",
        "```python\n",
        "series.str.find(sub, start=0, end=None)\n",
        "```\n",
        "\n",
        "### 参数\n",
        "- `sub`: 要查找的子串\n",
        "- `start`: 开始搜索的位置（默认0）\n",
        "- `end`: 结束搜索的位置（默认None，表示到字符串末尾）\n",
        "\n",
        "### 返回值\n",
        "- 找到：返回子串第一次出现的索引位置\n",
        "- 找不到：返回-1\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "原始数据:\n",
            "0    Python Programming\n",
            "1           Hello World\n",
            "2          Data Science\n",
            "3      Machine Learning\n",
            "dtype: object\n",
            "\n",
            "查找'Python'的位置:\n",
            "0    0\n",
            "1   -1\n",
            "2   -1\n",
            "3   -1\n",
            "dtype: int64\n",
            "\n",
            "查找'World'的位置:\n",
            "0   -1\n",
            "1    6\n",
            "2   -1\n",
            "3   -1\n",
            "dtype: int64\n",
            "\n",
            "查找'Java'的位置（不存在）:\n",
            "0   -1\n",
            "1   -1\n",
            "2   -1\n",
            "3   -1\n",
            "dtype: int64\n"
          ]
        }
      ],
      "source": [
        "# 创建示例数据\n",
        "data = pd.Series(['Python Programming', 'Hello World', 'Data Science', 'Machine Learning'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 查找'Python'的位置\n",
        "print(\"\\n查找'Python'的位置:\")\n",
        "print(data.str.find('Python'))\n",
        "\n",
        "# 查找'World'的位置\n",
        "print(\"\\n查找'World'的位置:\")\n",
        "print(data.str.find('World'))\n",
        "\n",
        "# 查找不存在的子串（返回-1）\n",
        "print(\"\\n查找'Java'的位置（不存在）:\")\n",
        "print(data.str.find('Java'))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 5.1 指定搜索范围\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "原始数据:\n",
            "0            hello hello world\n",
            "1    python python programming\n",
            "dtype: object\n",
            "\n",
            "在整个字符串中查找'hello':\n",
            "0    0\n",
            "1   -1\n",
            "dtype: int64\n",
            "\n",
            "从第5个字符开始查找'hello':\n",
            "0    6\n",
            "1   -1\n",
            "dtype: int64\n",
            "\n",
            "在前10个字符中查找'hello':\n",
            "0    0\n",
            "1   -1\n",
            "dtype: int64\n"
          ]
        }
      ],
      "source": [
        "# 创建包含重复子串的数据\n",
        "data = pd.Series(['hello hello world', 'python python programming'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 在整个字符串中查找'hello'\n",
        "print(\"\\n在整个字符串中查找'hello':\")\n",
        "print(data.str.find('hello'))\n",
        "\n",
        "# 从第5个字符开始查找'hello'\n",
        "print(\"\\n从第5个字符开始查找'hello':\")\n",
        "print(data.str.find('hello', start=5))\n",
        "\n",
        "# 在前10个字符中查找'hello'\n",
        "print(\"\\n在前10个字符中查找'hello':\")\n",
        "print(data.str.find('hello', start=0, end=10))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 6. str.rfind() - 从右侧查找子串位置\n",
        "\n",
        "`str.rfind()`方法从字符串的右侧（末尾）开始查找子串，返回最后一次出现的位置。\n",
        "\n",
        "### 语法\n",
        "```python\n",
        "series.str.rfind(sub, start=0, end=None)\n",
        "```\n",
        "\n",
        "### 参数\n",
        "- `sub`: 要查找的子串\n",
        "- `start`: 开始搜索的位置（默认0）\n",
        "- `end`: 结束搜索的位置（默认None）\n",
        "\n",
        "### 返回值\n",
        "- 找到：返回子串最后一次出现的索引位置\n",
        "- 找不到：返回-1\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "原始数据:\n",
            "0            hello world hello\n",
            "1    python python programming\n",
            "2                    test test\n",
            "dtype: object\n",
            "\n",
            "使用find()查找'hello'（第一次出现）:\n",
            "0    0\n",
            "1   -1\n",
            "2   -1\n",
            "dtype: int64\n",
            "\n",
            "使用rfind()查找'hello'（最后一次出现）:\n",
            "0    12\n",
            "1    -1\n",
            "2    -1\n",
            "dtype: int64\n",
            "\n",
            "对比find()和rfind()查找'python':\n",
            "find(): [-1, 0, -1]\n",
            "rfind(): [-1, 7, -1]\n"
          ]
        }
      ],
      "source": [
        "# 创建包含重复子串的数据\n",
        "data = pd.Series(['hello world hello', 'python python programming', 'test test'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 使用find()查找（从左侧，返回第一次出现的位置）\n",
        "print(\"\\n使用find()查找'hello'（第一次出现）:\")\n",
        "print(data.str.find('hello'))\n",
        "\n",
        "# 使用rfind()查找（从右侧，返回最后一次出现的位置）\n",
        "print(\"\\n使用rfind()查找'hello'（最后一次出现）:\")\n",
        "print(data.str.rfind('hello'))\n",
        "\n",
        "# 对比find()和rfind()\n",
        "print(\"\\n对比find()和rfind()查找'python':\")\n",
        "print(\"find():\", data.str.find('python').tolist())\n",
        "print(\"rfind():\", data.str.rfind('python').tolist())\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 7. str.index() - 查找子串索引（找不到抛异常）\n",
        "\n",
        "`str.index()`方法与`str.find()`类似，但如果找不到子串会抛出异常而不是返回-1。\n",
        "\n",
        "### 语法\n",
        "```python\n",
        "series.str.index(sub, start=0, end=None)\n",
        "```\n",
        "\n",
        "### 参数\n",
        "- `sub`: 要查找的子串\n",
        "- `start`: 开始搜索的位置（默认0）\n",
        "- `end`: 结束搜索的位置（默认None）\n",
        "\n",
        "### 返回值\n",
        "- 找到：返回子串第一次出现的索引位置\n",
        "- 找不到：抛出ValueError异常\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "原始数据:\n",
            "0    Python Programming\n",
            "1           Hello World\n",
            "2          Data Science\n",
            "dtype: object\n",
            "\n",
            "使用index()查找'Python':\n"
          ]
        },
        {
          "ename": "ValueError",
          "evalue": "substring not found",
          "output_type": "error",
          "traceback": [
            "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
            "\u001b[31mValueError\u001b[39m                                Traceback (most recent call last)",
            "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[11]\u001b[39m\u001b[32m, line 8\u001b[39m\n\u001b[32m      6\u001b[39m \u001b[38;5;66;03m# 使用index()查找存在的子串\u001b[39;00m\n\u001b[32m      7\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[33m使用index()查找\u001b[39m\u001b[33m'\u001b[39m\u001b[33mPython\u001b[39m\u001b[33m'\u001b[39m\u001b[33m:\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[43mdata\u001b[49m\u001b[43m.\u001b[49m\u001b[43mstr\u001b[49m\u001b[43m.\u001b[49m\u001b[43mindex\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43mPython\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m)\n\u001b[32m     10\u001b[39m \u001b[38;5;66;03m# 使用index()查找不存在的子串（会抛出异常）\u001b[39;00m\n\u001b[32m     11\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n",
            "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/ml311/lib/python3.11/site-packages/pandas/core/strings/accessor.py:140\u001b[39m, in \u001b[36mforbid_nonstring_types.<locals>._forbid_nonstring_types.<locals>.wrapper\u001b[39m\u001b[34m(self, *args, **kwargs)\u001b[39m\n\u001b[32m    135\u001b[39m     msg = (\n\u001b[32m    136\u001b[39m         \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mCannot use .str.\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfunc_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m with values of \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m    137\u001b[39m         \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33minferred dtype \u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mself\u001b[39m._inferred_dtype\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m.\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m    138\u001b[39m     )\n\u001b[32m    139\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(msg)\n\u001b[32m--> \u001b[39m\u001b[32m140\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
            "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/ml311/lib/python3.11/site-packages/pandas/core/strings/accessor.py:3055\u001b[39m, in \u001b[36mStringMethods.index\u001b[39m\u001b[34m(self, sub, start, end)\u001b[39m\n\u001b[32m   3052\u001b[39m     msg = \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mexpected a string object, not \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mtype\u001b[39m(sub).\u001b[34m__name__\u001b[39m\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m   3053\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(msg)\n\u001b[32m-> \u001b[39m\u001b[32m3055\u001b[39m result = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_data\u001b[49m\u001b[43m.\u001b[49m\u001b[43marray\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_str_index\u001b[49m\u001b[43m(\u001b[49m\u001b[43msub\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstart\u001b[49m\u001b[43m=\u001b[49m\u001b[43mstart\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mend\u001b[49m\u001b[43m=\u001b[49m\u001b[43mend\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   3056\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._wrap_result(result, returns_string=\u001b[38;5;28;01mFalse\u001b[39;00m)\n",
            "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/ml311/lib/python3.11/site-packages/pandas/core/strings/object_array.py:319\u001b[39m, in \u001b[36mObjectStringArrayMixin._str_index\u001b[39m\u001b[34m(self, sub, start, end)\u001b[39m\n\u001b[32m    317\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m    318\u001b[39m     f = \u001b[38;5;28;01mlambda\u001b[39;00m x: x.index(sub, start, end)\n\u001b[32m--> \u001b[39m\u001b[32m319\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_str_map\u001b[49m\u001b[43m(\u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mint64\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
            "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/ml311/lib/python3.11/site-packages/pandas/core/strings/object_array.py:82\u001b[39m, in \u001b[36mObjectStringArrayMixin._str_map\u001b[39m\u001b[34m(self, f, na_value, dtype, convert)\u001b[39m\n\u001b[32m     80\u001b[39m map_convert = convert \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m np.all(mask)\n\u001b[32m     81\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m82\u001b[39m     result = \u001b[43mlib\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmap_infer_mask\u001b[49m\u001b[43m(\u001b[49m\u001b[43marr\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mf\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmask\u001b[49m\u001b[43m.\u001b[49m\u001b[43mview\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnp\u001b[49m\u001b[43m.\u001b[49m\u001b[43muint8\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmap_convert\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m     83\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m (\u001b[38;5;167;01mTypeError\u001b[39;00m, \u001b[38;5;167;01mAttributeError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[32m     84\u001b[39m     \u001b[38;5;66;03m# Reraise the exception if callable `f` got wrong number of args.\u001b[39;00m\n\u001b[32m     85\u001b[39m     \u001b[38;5;66;03m# The user may want to be warned by this, instead of getting NaN\u001b[39;00m\n\u001b[32m     86\u001b[39m     p_err = (\n\u001b[32m     87\u001b[39m         \u001b[33mr\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m((takes)|(missing)) (?(2)from \u001b[39m\u001b[33m\\\u001b[39m\u001b[33md+ to )?\u001b[39m\u001b[33m\\\u001b[39m\u001b[33md+ \u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m     88\u001b[39m         \u001b[33mr\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m(?(3)required )positional arguments?\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m     89\u001b[39m     )\n",
            "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/lib.pyx:2922\u001b[39m, in \u001b[36mpandas._libs.lib.map_infer_mask\u001b[39m\u001b[34m()\u001b[39m\n",
            "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/lib.pyx:2959\u001b[39m, in \u001b[36mpandas._libs.lib._map_infer_mask\u001b[39m\u001b[34m()\u001b[39m\n",
            "\u001b[36mFile \u001b[39m\u001b[32m/opt/anaconda3/envs/ml311/lib/python3.11/site-packages/pandas/core/strings/object_array.py:318\u001b[39m, in \u001b[36mObjectStringArrayMixin._str_index.<locals>.<lambda>\u001b[39m\u001b[34m(x)\u001b[39m\n\u001b[32m    316\u001b[39m     f = \u001b[38;5;28;01mlambda\u001b[39;00m x: x.index(sub, start, end)\n\u001b[32m    317\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m318\u001b[39m     f = \u001b[38;5;28;01mlambda\u001b[39;00m x: \u001b[43mx\u001b[49m\u001b[43m.\u001b[49m\u001b[43mindex\u001b[49m\u001b[43m(\u001b[49m\u001b[43msub\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstart\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mend\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    319\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._str_map(f, dtype=\u001b[33m\"\u001b[39m\u001b[33mint64\u001b[39m\u001b[33m\"\u001b[39m)\n",
            "\u001b[31mValueError\u001b[39m: substring not found"
          ]
        }
      ],
      "source": [
        "# 创建示例数据\n",
        "data = pd.Series(['Python Programming', 'Hello World', 'Data Science'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 使用index()查找存在的子串\n",
        "print(\"\\n使用index()查找'Python':\")\n",
        "print(data.str.index('Python'))\n",
        "\n",
        "# 使用index()查找不存在的子串（会抛出异常）\n",
        "try:\n",
        "    result = data.str.index('Java')\n",
        "    print(\"\\n使用index()查找'Java':\")\n",
        "    print(result)\n",
        "except ValueError as e:\n",
        "    print(\"\\n使用index()查找'Java'时抛出异常:\")\n",
        "    print(f\"错误: {e}\")\n",
        "\n",
        "# 对比find()和index()\n",
        "print(\"\\n对比find()和index()查找不存在的子串:\")\n",
        "print(\"find()返回-1:\", data.str.find('Java').tolist())\n",
        "print(\"index()会抛出异常（已捕获）\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 8. str.rindex() - 从右侧查找子串索引\n",
        "\n",
        "`str.rindex()`方法从右侧查找子串，如果找不到会抛出异常。\n",
        "\n",
        "### 语法\n",
        "```python\n",
        "series.str.rindex(sub, start=0, end=None)\n",
        "```\n",
        "\n",
        "### 参数\n",
        "- `sub`: 要查找的子串\n",
        "- `start`: 开始搜索的位置（默认0）\n",
        "- `end`: 结束搜索的位置（默认None）\n",
        "\n",
        "### 返回值\n",
        "- 找到：返回子串最后一次出现的索引位置\n",
        "- 找不到：抛出ValueError异常\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建包含重复子串的数据\n",
        "data = pd.Series(['hello world hello', 'python python programming'])\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 使用rindex()查找（从右侧，返回最后一次出现的位置）\n",
        "print(\"\\n使用rindex()查找'hello'（最后一次出现）:\")\n",
        "print(data.str.rindex('hello'))\n",
        "\n",
        "# 对比rfind()和rindex()\n",
        "print(\"\\n对比rfind()和rindex():\")\n",
        "print(\"rfind():\", data.str.rfind('hello').tolist())\n",
        "print(\"rindex():\", data.str.rindex('hello').tolist())\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 9. 方法对比总结\n",
        "\n",
        "| 方法 | 功能 | 找不到时的行为 | 查找方向 |\n",
        "|------|------|---------------|---------|\n",
        "| `str.contains()` | 检查是否包含 | 返回False | 从左到右 |\n",
        "| `str.startswith()` | 检查开头 | 返回False | 开头 |\n",
        "| `str.endswith()` | 检查结尾 | 返回False | 结尾 |\n",
        "| `str.find()` | 查找位置 | 返回-1 | 从左到右（第一次） |\n",
        "| `str.rfind()` | 查找位置 | 返回-1 | 从右到左（最后一次） |\n",
        "| `str.index()` | 查找索引 | 抛出异常 | 从左到右（第一次） |\n",
        "| `str.rindex()` | 查找索引 | 抛出异常 | 从右到左（最后一次） |\n",
        "\n",
        "### 选择建议\n",
        "\n",
        "- **检查是否存在**: 使用`contains()`、`startswith()`或`endswith()`\n",
        "- **查找位置（安全）**: 使用`find()`或`rfind()`（不会抛异常）\n",
        "- **查找位置（严格）**: 使用`index()`或`rindex()`（找不到会抛异常，适合确保一定存在的情况）\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 10. 实际应用案例\n",
        "\n",
        "### 10.1 数据筛选：根据内容过滤\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建产品数据\n",
        "products = pd.DataFrame({\n",
        "    'name': ['iPhone 14', 'Samsung Galaxy', 'iPhone 13', 'iPad Pro', 'MacBook Pro'],\n",
        "    'price': [6999, 5999, 5999, 7999, 12999],\n",
        "    'category': ['手机', '手机', '手机', '平板', '笔记本']\n",
        "})\n",
        "\n",
        "print(\"原始产品数据:\")\n",
        "print(products)\n",
        "\n",
        "# 筛选包含'iPhone'的产品\n",
        "iphone_products = products[products['name'].str.contains('iPhone', case=False)]\n",
        "print(\"\\n包含'iPhone'的产品:\")\n",
        "print(iphone_products)\n",
        "\n",
        "# 筛选以'Mac'开头的产品\n",
        "mac_products = products[products['name'].str.startswith('Mac')]\n",
        "print(\"\\n以'Mac'开头的产品:\")\n",
        "print(mac_products)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 10.2 文件类型分类\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建文件列表\n",
        "files = pd.DataFrame({\n",
        "    'filename': [\n",
        "        'document.pdf', 'image.jpg', 'data.xlsx', 'script.py',\n",
        "        'photo.png', 'report.docx', 'code.js', 'video.mp4'\n",
        "    ]\n",
        "})\n",
        "\n",
        "print(\"文件列表:\")\n",
        "print(files)\n",
        "\n",
        "# 根据文件扩展名分类\n",
        "files['is_image'] = files['filename'].str.endswith(('.jpg', '.png', '.gif'))\n",
        "files['is_document'] = files['filename'].str.endswith(('.pdf', '.docx', '.xlsx'))\n",
        "files['is_code'] = files['filename'].str.endswith(('.py', '.js', '.java'))\n",
        "\n",
        "print(\"\\n文件分类结果:\")\n",
        "print(files)\n",
        "\n",
        "# 统计各类文件数量\n",
        "print(\"\\n文件类型统计:\")\n",
        "print(f\"图片文件: {files['is_image'].sum()}个\")\n",
        "print(f\"文档文件: {files['is_document'].sum()}个\")\n",
        "print(f\"代码文件: {files['is_code'].sum()}个\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 10.3 邮箱域名提取和分类\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建邮箱数据\n",
        "emails = pd.DataFrame({\n",
        "    'email': [\n",
        "        'alice@gmail.com',\n",
        "        'bob@yahoo.com',\n",
        "        'charlie@gmail.com',\n",
        "        'david@outlook.com',\n",
        "        'eve@gmail.com'\n",
        "    ]\n",
        "})\n",
        "\n",
        "print(\"邮箱数据:\")\n",
        "print(emails)\n",
        "\n",
        "# 使用find()找到@符号的位置，然后提取域名\n",
        "emails['at_position'] = emails['email'].str.find('@')\n",
        "emails['domain'] = emails.apply(\n",
        "    lambda row: row['email'][row['at_position']+1:] if row['at_position'] != -1 else None,\n",
        "    axis=1\n",
        ")\n",
        "\n",
        "# 分类邮箱（gmail、yahoo、其他）\n",
        "emails['is_gmail'] = emails['email'].str.contains('@gmail.com')\n",
        "emails['is_yahoo'] = emails['email'].str.contains('@yahoo.com')\n",
        "emails['email_type'] = emails.apply(\n",
        "    lambda row: 'Gmail' if row['is_gmail'] else ('Yahoo' if row['is_yahoo'] else '其他'),\n",
        "    axis=1\n",
        ")\n",
        "\n",
        "print(\"\\n邮箱分析结果:\")\n",
        "print(emails[['email', 'domain', 'email_type']])\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 10.4 数据验证：检查格式\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建用户数据\n",
        "users = pd.DataFrame({\n",
        "    'username': ['alice123', 'bob', 'charlie_2024', 'david', 'eve_smith'],\n",
        "    'email': ['alice@example.com', 'bob@test.com', 'invalid_email', 'david@demo.com', 'eve@mail.com']\n",
        "})\n",
        "\n",
        "print(\"用户数据:\")\n",
        "print(users)\n",
        "\n",
        "# 验证用户名格式（要求包含字母和数字）\n",
        "users['has_letter'] = users['username'].str.contains(r'[a-zA-Z]', regex=True)\n",
        "users['has_digit'] = users['username'].str.contains(r'\\d', regex=True)\n",
        "users['username_valid'] = users['has_letter'] & users['has_digit']\n",
        "\n",
        "# 验证邮箱格式（必须包含@符号）\n",
        "users['email_valid'] = users['email'].str.contains('@')\n",
        "\n",
        "print(\"\\n验证结果:\")\n",
        "print(users[['username', 'username_valid', 'email', 'email_valid']])\n",
        "\n",
        "# 找出无效数据\n",
        "invalid_users = users[~(users['username_valid'] & users['email_valid'])]\n",
        "print(\"\\n无效用户数据:\")\n",
        "print(invalid_users[['username', 'email']])\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 10.5 文本分析：查找关键词位置\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建文章标题数据\n",
        "articles = pd.DataFrame({\n",
        "    'title': [\n",
        "        'Introduction to Python Programming',\n",
        "        'Advanced Python Techniques',\n",
        "        'Python for Data Science',\n",
        "        'Machine Learning with Python',\n",
        "        'Python Best Practices'\n",
        "    ]\n",
        "})\n",
        "\n",
        "print(\"文章标题:\")\n",
        "print(articles)\n",
        "\n",
        "# 查找'Python'在标题中的位置\n",
        "articles['python_position'] = articles['title'].str.find('Python')\n",
        "articles['has_python'] = articles['python_position'] != -1\n",
        "\n",
        "# 分析Python在标题中的位置\n",
        "print(\"\\n分析结果:\")\n",
        "print(articles[['title', 'python_position', 'has_python']])\n",
        "\n",
        "# 统计Python出现的位置分布\n",
        "print(\"\\nPython位置统计:\")\n",
        "print(f\"在开头的标题数: {(articles['python_position'] == 0).sum()}\")\n",
        "print(f\"在中间的标题数: {((articles['python_position'] > 0) & (articles['python_position'] != -1)).sum()}\")\n",
        "print(f\"不包含Python的标题数: {(articles['python_position'] == -1).sum()}\")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 11. 注意事项和最佳实践\n",
        "\n",
        "### 11.1 find() vs index()的选择\n",
        "\n",
        "- **find()**: 更安全，找不到返回-1，不会中断程序\n",
        "- **index()**: 更严格，找不到会抛异常，适合确保子串一定存在的情况\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 推荐使用find()（更安全）\n",
        "data = pd.Series(['Python', 'Pandas', 'NumPy'])\n",
        "result = data.str.find('Java')  # 返回-1，不会抛异常\n",
        "print(\"使用find()（安全）:\")\n",
        "print(result)\n",
        "\n",
        "# 如果确定子串存在，可以使用index()\n",
        "# 但通常find()更推荐，因为可以检查返回值\n",
        "data_with_java = pd.Series(['Java Programming', 'Python', 'Java Basics'])\n",
        "result = data_with_java.str.find('Java')\n",
        "print(\"\\n使用find()查找'Java':\")\n",
        "print(result)\n",
        "print(\"找到的位置:\", result[result != -1].tolist())\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 11.2 contains()的正则表达式\n",
        "\n",
        "`contains()`默认使用正则表达式，如果只想进行字面匹配，需要转义特殊字符或设置`regex=False`。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 创建包含特殊字符的数据\n",
        "data = pd.Series(['test.com', 'test@com', 'test+com'])\n",
        "\n",
        "# 查找包含'.'的字符串（.在正则表达式中是特殊字符）\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 使用正则表达式（需要转义）\n",
        "print(\"\\n使用正则表达式查找'.'（需要转义）:\")\n",
        "print(data.str.contains(r'\\.', regex=True))\n",
        "\n",
        "# 不使用正则表达式（字面匹配）\n",
        "print(\"\\n不使用正则表达式查找'.'（字面匹配）:\")\n",
        "print(data.str.contains('.', regex=False))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### 11.3 链式操作\n",
        "\n",
        "这些查找方法可以与其他字符串方法链式使用。\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# 链式操作示例\n",
        "data = pd.Series(['  Python Programming  ', '  PANDAS Analysis  ', '  NumPy Arrays  '])\n",
        "\n",
        "print(\"原始数据:\")\n",
        "print(data)\n",
        "\n",
        "# 链式操作：去除空白 -> 转小写 -> 检查是否包含'python'\n",
        "result = data.str.strip().str.lower().str.contains('python')\n",
        "print(\"\\n链式操作结果（去除空白 -> 转小写 -> 检查包含'python'）:\")\n",
        "print(result)\n",
        "\n",
        "# 链式操作：去除空白 -> 查找子串位置\n",
        "position = data.str.strip().str.find('Python')\n",
        "print(\"\\n去除空白后查找'Python'的位置:\")\n",
        "print(position)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## 12. 总结\n",
        "\n",
        "### 12.1 关键要点\n",
        "\n",
        "1. **str.contains()**: 检查是否包含子串，支持正则表达式，返回布尔Series\n",
        "   - 适用于数据筛选和条件判断\n",
        "   - 可以设置大小写敏感和NaN处理方式\n",
        "\n",
        "2. **str.startswith()** 和 **str.endswith()**: 检查字符串开头和结尾\n",
        "   - 适用于文件类型判断、前缀/后缀验证\n",
        "\n",
        "3. **str.find()** 和 **str.rfind()**: 查找子串位置，找不到返回-1\n",
        "   - 更安全，不会抛异常\n",
        "   - find()从左侧查找第一次出现，rfind()从右侧查找最后一次出现\n",
        "\n",
        "4. **str.index()** 和 **str.rindex()**: 查找子串索引，找不到抛异常\n",
        "   - 更严格，适合确保子串一定存在的情况\n",
        "   - index()从左侧查找第一次出现，rindex()从右侧查找最后一次出现\n",
        "\n",
        "### 12.2 应用场景\n",
        "\n",
        "- **数据筛选**: 使用contains()、startswith()、endswith()过滤数据\n",
        "- **格式验证**: 检查字符串格式是否符合要求\n",
        "- **文本分析**: 查找关键词位置，分析文本特征\n",
        "- **文件分类**: 根据文件扩展名或路径进行分类\n",
        "- **数据清洗**: 识别和筛选特定模式的数据\n",
        "\n",
        "### 12.3 最佳实践\n",
        "\n",
        "- 优先使用`find()`而不是`index()`（更安全）\n",
        "- 使用`contains()`进行数据筛选时注意正则表达式的使用\n",
        "- 链式调用多个字符串方法可以简化代码\n",
        "- 对于大量数据，这些方法都是向量化的，性能很好\n",
        "- 注意处理NaN值，根据需求设置`na`参数\n",
        "\n",
        "### 12.4 下一步学习\n",
        "\n",
        "掌握了字符串查找和匹配后，可以继续学习：\n",
        "- 字符串替换（str.replace, str.translate）\n",
        "- 字符串分割和连接\n",
        "- 字符串格式化\n",
        "- 更多正则表达式操作\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "ml311",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
